﻿-- Assert that all dependencies are available
assert(LibStub, "LibTweetCraft-1.2 requires LibStub")
assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibTweetCraft-1.2 requires CallbackHandler-1.0")
assert(LibStub:GetLibrary("LibDebugLog-1.0", true), "LibTweetCraft-1.2 requires LibDebugLog-1.0")

-- Register LibTweetCraft 1.2 using LibStub
local MAJOR, MINOR = "LibTweetCraft-1.2", 1
local LibTweetCraft = LibStub:NewLibrary(MAJOR, MINOR)

if not LibTweetCraft then return end -- No upgrade needed

-- Initialize TweetCraft options
tweetCraftOptions = { AddOns = {} }

-- Initialize private fields
LibTweetCraft.tweets = {}
LibTweetCraft.replies = {}
LibTweetCraft.outgoingTweets = {}
LibTweetCraft.addons = {}
LibTweetCraft.categories = {}

-- Make sure we have CallbackHandler at our disposal
LibTweetCraft.Callbacks = LibTweetCraft.Callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(LibTweetCraft)

-- Set the metatable for LibTweetCraft to return 'LibTweetCraft' for tostring()
setmetatable(LibTweetCraft, { __tostring = function(self) return "LibTweetCraft" end })

-- Embed LibDebugLog in LibTweetCraft
LibStub:GetLibrary("LibDebugLog-1.0"):Embed(LibTweetCraft)

-- Uncomment the following line to enable LibDebugLog globally (during development)
-- LibStub:GetLibrary("LibDebugLog-1.0"):SetGlobalToggle(true)

function find(table, name)
	for i, element in ipairs(table) do
		if element.name == name then
			return element
		end
	end
		
	return nil
end

--------------------------------------------------------------------------
-- CreateEventHandler(logger)
--  Creates an event handler frame that can handle World of Warcraft events
--
-- loger			- if available, events will be logged
function CreateEventHandler(logger)
	-- Create event handler frame
	local eventHandler = CreateFrame("FRAME")

	-- OnEvent implementation that delegates the handling of the event
	local function OnEvent(self, event, ...)
		if logger then
			-- Log the event
			logger:Debug(event .. '(' .. (... or '') .. ')')
		end

		-- Call the actual event handler
		self[event](self, ...)
	end

	-- Set OnEvent script
	eventHandler:SetScript("OnEvent", OnEvent)
	
	return eventHandler
end

-- Create an event handler for handling World of Warcraft events
LibTweetCraft.EventHandler = CreateEventHandler()

-- Register for the ADDON_LOADED and PLAYER_LOGOUT events
LibTweetCraft.EventHandler:RegisterEvent("ADDON_LOADED")
LibTweetCraft.EventHandler:RegisterEvent("PLAYER_LOGOUT")

function LibTweetCraft.EventHandler:ADDON_LOADED(...)
	local addOnName = ...
	
	if addOnName == "TweetCraft" then
		-- Initialize addons
		LibTweetCraft:InitializeAddOns()
		
		-- Instruct LibTweetCraft to receive tweets and replies
		LibTweetCraft:Receive()
	end
end

function LibTweetCraft.EventHandler:PLAYER_LOGOUT(...)
	-- Instruct LibTweetCraft to send outgoing tweets
	LibTweetCraft:Send()
end

--------------------------------------------------------------------------
-- LibTweetCraft:GetTweets(type)
--  Returns all tweets of a given type
--
-- type                   - the type of the tweet (IncomingTweet, Reply, OutgoingTweet)
--
function LibTweetCraft:GetTweets(type)
	local tweets = {}
	
	self:Debug("GetTweets: type = %s", type)
	
	-- Get the tweets of the given type
	if (type == 'IncomingTweet') then
		tweets = self.tweets
	elseif (type == 'Reply') then
		tweets = self.replies
	elseif (type == 'OutgoingTweet') then
		tweets = self.outgoingTweets
	end
	
	-- Sort tweets by DateSent
	table.sort(tweets, function(left, right) return left.DateSent > right.DateSent end)
	
	return tweets
end

--------------------------------------------------------------------------
-- LibTweetCraft:GetIncomingTweets()
--  Returns all incoming tweets
--
function LibTweetCraft:GetIncomingTweets()
	return self:GetTweets("IncomingTweet")
end

--------------------------------------------------------------------------
-- LibTweetCraft:GetReplies()
--  Returns all replies
--
function LibTweetCraft:GetReplies()
	return self:GetTweets("Reply")
end

--------------------------------------------------------------------------
-- LibTweetCraft:GetOutgoingTweets()
--  Returns all outgoing (queued) tweets
--
function LibTweetCraft:GetOutgoingTweets()
	return self:GetTweets("OutgoingTweet")
end

--------------------------------------------------------------------------
-- LibTweetCraft:Send
--  Sends outgoing tweets to Twitter through saved variables
function LibTweetCraft:Refresh()
	ReloadUI()
end

--------------------------------------------------------------------------
-- LibTweetCraft:IsValidIncomingTweet(tweet)
--  Checks whether an incoming tweet contains fields of the correct type
function LibTweetCraft:IsValidIncomingTweet(tweet)
	return type(tweet.Id) == "number" and
		   type(tweet.DateSent) == "number" and
		   type(tweet.RelativeDateSent) == "string" and
		   type(tweet.Message) == "string" and
		   type(tweet.User.Id) == "number" and
		   type(tweet.User.ScreenName) == "string"
end

--------------------------------------------------------------------------
-- LibTweetCraft:IsValidIncomingTweet(tweet)
--  Checks whether an outgoing tweet contains fields of the correct type
function LibTweetCraft:IsValidOutgoingTweet(tweet)
	return type(tweet.DateSent) == "number" and
		   type(tweet.Message) == "string"
end

--------------------------------------------------------------------------
-- LibTweetCraft:Receive
--  Receives incoming tweets through saved variables and creates 
--  data objects.
--
function LibTweetCraft:Receive()
	if tweets then
		for i, tweet in ipairs(tweets) do
		
			-- Check whether tweet is valid
			if (self:IsValidIncomingTweet(tweet)) then
				-- Add tweet to internal collection of tweets
				self.tweets[#self.tweets + 1] = 
				 {
					type = "IncomingTweet",
					Id = tweet.Id,
					DateSent = tweet.DateSent,
					RelativeDateSent = tweet.RelativeDateSent,
					Message = tweet.Message,
					User = tweet.User,
				}
			end
		end
		
		self.Callbacks:Fire("LibTweetCraft_TweetsChanged")
	end
	
	-- Iterate through the replies table and create data objects
	if replies then
		for i, tweet in ipairs(replies) do
		
			-- Check whether tweet is valid
			if (self:IsValidIncomingTweet(tweet)) then
				-- Add tweet to internal collection of replies
				self.replies[#self.replies + 1] = 
				 {
					type = "Reply",
					Id = tweet.Id,
					DateSent = tweet.DateSent,
					RelativeDateSent = tweet.RelativeDateSent,
					Message = tweet.Message,
					User = tweet.User,
				}			
			end
		end
		
		self.Callbacks:Fire("LibTweetCraft_RepliesChanged")
		
	end

	-- Iterate through the outgoingTweets table and create data objects
	if outgoingTweets then
		for i, tweet in ipairs(outgoingTweets) do

			-- Check whether tweet is valid
			if (self:IsValidOutgoingTweet(tweet)) then
				self.outgoingTweets[#self.outgoingTweets + 1] =
				{
					type = "OutgoingTweet",
					DateSent = tweet.DateSent,
					Message = tweet.Message,
					User = { ScreenName = "You" }
				}
			end
		end
		
		self.Callbacks:Fire("LibTweetCraft_OutgoingTweetsChanged")
	end
end

--------------------------------------------------------------------------
-- LibTweetCraft:Send
--  Sends outgoing tweets to Twitter through saved variables
--
-- refresh                  - a value indicating whether to perform a ReloadUI()
function LibTweetCraft:Send(refresh)
	-- Clear the outgoingTweets variable
	outgoingTweets = {}

	-- Iterate through the data objects and updated outgoingTweets saved variable
	for i, tweet in ipairs(self.outgoingTweets) do
		outgoingTweets[#outgoingTweets + 1] = { 
			DateSent = tweet.DateSent,
			Message = tweet.Message 
		}
	end

	if refresh then
		-- Trigger a UI reload to refresh the saved variables file
		self:Refresh()
	end
end


--------------------------------------------------------------------------
-- LibTweetCraft:Tweet
--  Queues a tweet to be sent by TweetCraft.
--
-- message               - tweet message
function LibTweetCraft:Tweet(message)
	self:AddOutgoingTweet("TweetCraft", "Manual", message)
end

function LibTweetCraft:AddOutgoingTweet(source, category, message)
	self:Debug("AddOutgoingTweet: source = %s, category = %s, message = %s", source, category, message)

	self.outgoingTweets[#self.outgoingTweets + 1] =
	{
		type = "OutgoingTweet",
		source = source,
		category = category,
		DateSent = time(),
		Message = message,
		User = { ScreenName = "You" }
	}
	
	self.Callbacks:Fire("LibTweetCraft_OutgoingTweetsChanged")
end

function LibTweetCraft:RemoveOutgoingTweet(tweet)
	self:Debug("DeleteOutgoingTweet: source = %s, category = %s, message = %s", tweet.source, tweet.category, tweet.Message)

	-- Iterate through the outgoing tweet table and remove the first occurence of tweet
	for i, outgoingTweet in ipairs(self.outgoingTweets) do
		if outgoingTweet == tweet then
			table.remove(self.outgoingTweets, i)
			
			-- Notify that the table changed
			self.Callbacks:Fire("LibTweetCraft_OutgoingTweetsChanged")
		end 
	end
end

--------------------------------------------------------------------------
-- LibTweetCraft:GetIdentity
--  Gets a number that can uniquely identify a data object
--
function LibTweetCraft:GetIdentity()
	-- Default the value of Identity to 0
	self.Identity = self.Identity or 0
	
	-- Increment the Identity value
	self.Identity = self.Identity + 1
	
	-- Return the Identity value
	return self.Identity
end

--------------------------------------------------------------------------
-- LibTweetCraft:GetAddOns()
--  Gets the AutoTweet addons
--
function LibTweetCraft:GetAddOns()
	return self.addons
end

--------------------------------------------------------------------------
-- LibTweetCraft:GetCategories()
--  Gets the AutoTweet categories added by registered addons
--
function LibTweetCraft:GetCategories()
	return self.categories
end

--------------------------------------------------------------------------
-- LibTweetCraft:Enable(name)
--  Enables an addon or a category
--
-- name                      - name of the addon or category
--
function LibTweetCraft:Enable(name)
	-- Find the category by name
	local category = find(self.categories, name)
	if category then
		category.enabled = true
	end
end

--------------------------------------------------------------------------
-- LibTweetCraft:Disable(name)
--  Disables an addon or a category
--
-- name                      - name of the addon or category
--
function LibTweetCraft:Disable(name)
	-- Find the category by name
	local category = find(self.categories, name)
	if category then
		category.enabled = false
	end
end

--------------------------------------------------------------------------
-- AddOn functions
--  Contains the functions that are applied to addons
--
local addOnFunctions = {}

--------------------------------------------------------------------------
-- addOnFunctions:GetName
--  Gets the name of the addon
--
addOnFunctions.GetName = function(self)
	return self.name
end

--------------------------------------------------------------------------
-- addOnFunctions:AddCategory
--  Adds a category that the addon provides AutoTweets for
--
-- name                 - name of the category
-- description          - description of the category
-- enabled              - default state of the category
--
addOnFunctions.AddCategory = function(self, name, displayName, description, enabled)
	-- Default enabled to true
	if enabled == nil then
	  enabled = true
	end

	self:Debug("AddCategory: name = %s, displayName = %s, description = %s, enabled = %s", name, displayName, description, enabled)
	
	-- Check whether a data object already exists with the name
	if find(LibTweetCraft.categories, name) then return end
	
	-- Make sure we have options for the addon
	tweetCraftOptions.AddOns[self.name] = tweetCraftOptions.AddOns[self.name] or { Categories = { } }
	
	-- Make sure we have options for the category
	tweetCraftOptions.AddOns[self.name].Categories[name] = tweetCraftOptions.AddOns[self.name].Categories[name] or { Enabled = enabled }
	
	-- Add a new 'AutoTweetCategory' data object
	LibTweetCraft.categories[#LibTweetCraft.categories + 1] = 
	{
		type = "AutoTweetCategory",
		name = name,
		displayName = displayName,
		addOn = self,
		description = description
	}
end

--------------------------------------------------------------------------
-- addOnFunctions:GetCategories
--  Gets the categories added by the addon
--
addOnFunctions.GetCategories = function(self)
	local addOnCategories = {}
	
	-- Iterate through all categories and return the ones that belong to self
	for i, category in ipairs(LibTweetCraft.categories) do
		if category.addOn == self then
			addOnCategories[#addOnCategories + 1] = category		
		end
	end
	
	return addOnCategories
end

addOnFunctions.IsCategoryEnabled = function(self, categoryName)
	-- Retrieve the category
	local category = find(LibTweetCraft.categories, categoryName)

	-- Tweet if category exists and both addon and category is enabled
	return (category and tweetCraftOptions.AddOns[self.name] and 
			tweetCraftOptions.AddOns[self.name].Categories[categoryName] and
	        tweetCraftOptions.AddOns[self.name].Categories[categoryName].Enabled)
end

--------------------------------------------------------------------------
-- addOnFunctions:Tweet(categoryName, message)
--  Adds an outgoing AutoTweet
--
-- categoryName             - name of the category
-- message                  - AutoTweet message
--
addOnFunctions.Tweet = function(self, categoryName, message)
	-- Tweet if category is enabled
	if self:IsCategoryEnabled(categoryName) then
		LibTweetCraft:AddOutgoingTweet(self.name, category, message)
		
		print("AutoTweeted '" .. message .. "'")
	end
end

--------------------------------------------------------------------------
-- LibTweetCraft:RegisterAutoTweetAddOn(name)
--  Registers an AutoTweet addon that can automatically tweet.
--
-- name                    - the name of the addon
-- description             - the description of the addon
-- enabled                 - default state of the addon
--
function LibTweetCraft:RegisterAutoTweetAddOn(name, description, enabled)
	-- Default enabled to true
	if enabled == nil then
	  enabled = true
	end
	
	-- Add a new 'AutoTweetAddOn' data object
	local addOn = 
	{
		type = "AutoTweetAddOn",
		name = name,
		description = description
	}
	
	self.addons[#self.addons + 1] = addOn
	
	self:Debug("RegisterAutoTweetAddOn: name = %s, description = %s", name, description)
	
	-- Apply the functions to the newly created addon data object
	for name, func in pairs(addOnFunctions) do
		addOn[name] = func
	end

	-- Embed logging for the addon
	LibStub:GetLibrary("LibDebugLog-1.0"):Embed(addOn)
	
	-- Create an event handler for the addon
	addOn.EventHandler = CreateEventHandler(addOn)
	
	return addOn
end

function LibTweetCraft:InitializeAddOns()
	self:Debug("InitializeAddOns")
	-- Iterate through all addons and initalize
	for i, addon in ipairs(LibTweetCraft.addons) do
		if addon.Initialize then
			addon:Initialize()
		end
	end
end